/**
 * @file ringbuffer.hpp
 * @author zhuanghj (zhuanghj550@163.com)
 * @brief ringbuff
 * @version 0.1
 * @date 2023-01-27
 *
 * @copyright Copyright (c) 2023
 *
 */
#ifndef __RINGBUFFER_HPP__
#define __RINGBUFFER_HPP__
#include <assert.h>
#include <errno.h>
#include <iostream>
#include <memory>
#include <mutex>
#include <string.h>
#define RINGBUFFER_DEFAULT_SIZE (4 * 1024 * 1024)
class RingBuffer
{
  private:
	int32_t capacity;
	int32_t remainingSize;
	uint8_t *readPtr;
	uint8_t *writePtr;
	uint8_t *headPtr;
	uint8_t *tailPtr;
	std::allocator<uint8_t> allocer;
	std::mutex memoryMutex;
	int32_t getDataSize();
	int32_t getRemainingSize();
	int32_t calcRemainingSize();

  public:
	RingBuffer() = delete;
	RingBuffer(int32_t size = RINGBUFFER_DEFAULT_SIZE);
	~RingBuffer();
	int32_t init();
	int32_t writeToRing(uint8_t *ptr, int32_t size);
	int32_t readFromRing(uint8_t *ptr, int32_t size);
};

/**
 * @brief Construct a new Ring Buffer:: Ring Buffer object
 *
 * @param size ring buff size
 */
RingBuffer::RingBuffer(int32_t size) : remainingSize(size), capacity(size) {}

/**
 * @brief initialize the ringbuff
 *
 * @return int32_t 0 for success, other for fail
 */
int32_t RingBuffer::init()
{
	std::lock_guard<std::mutex> lock(this->memoryMutex);
	if (this->headPtr != nullptr) {
		printf("ringbuffer is already initialized\n");
		return -EEXIST;
	}
	this->headPtr = this->allocer.allocate(this->capacity);
	if (this->headPtr == nullptr) {
		printf("ring buffer allocate fail %s\n", strerror(errno));
		perror("allocate fail");
		return errno;
	}
	this->tailPtr = this->headPtr + this->capacity;
	memset((char *)this->headPtr, 0, this->capacity);
	this->writePtr = this->headPtr;
	this->readPtr = this->headPtr;
	this->remainingSize = this->calcRemainingSize();
	return 0;
}

/**
 * @brief Destroy the Ring Buffer:: Ring Buffer object
 *
 */
RingBuffer::~RingBuffer()
{
	std::lock_guard<std::mutex> lock(this->memoryMutex);
	this->allocer.deallocate((unsigned char *)this->headPtr, this->capacity);
}

/**
 * @brief write data to the ring buff,
 *
 * @param ptr pointer that point at the start of the data buff
 * @param size size of the data buff
 * @return int32_t >=0 for actual byte write to the buff, <0 for fail
 */
int32_t RingBuffer::writeToRing(uint8_t *ptr, int32_t size)
{
	int32_t ret = 0;
	int32_t sizeCopyed = 0;
	assert(ptr != nullptr);
	assert(size >= 0);
	if (this->headPtr == nullptr) {
		printf("ring buffer is not initialized\n");
		return -EPERM;
	}
	if (size > this->getRemainingSize()) {
		printf("write %d bytes in but %d bytes left\n", size,
			   this->getRemainingSize());
	}
	std::lock_guard<std::mutex> lock(this->memoryMutex);
	if (size > this->remainingSize) {
		return -ENOSPC;
	}
	if (this->tailPtr - this->writePtr > size) {
		this->writePtr = (uint8_t *)mempcpy(this->writePtr, ptr, size);
	} else {
		memcpy(this->writePtr, ptr, this->tailPtr - this->writePtr);
		sizeCopyed = this->tailPtr - this->writePtr;
		this->writePtr = (uint8_t *)mempcpy(this->headPtr, ptr + sizeCopyed,
											size - sizeCopyed);
	}
	this->remainingSize = this->calcRemainingSize();

	return size;
}

/**
 * @brief read data from the ring buff,and move the read point
 *
 * @param ptr point
 * @param readSize read max size, if the data store in ringbuff is
 * smaller.return the data size int the ringbuff
 * @return int32_t return the actual buff size store in buff
 */
int32_t RingBuffer::readFromRing(uint8_t *ptr, int32_t readSize)
{
	int32_t ret = 0;
	int32_t size = 0;
	int32_t realDataSize = 0;
	int32_t copySize = 0;
	if (this->headPtr == nullptr) {
		printf("ring buffer is not initialized\n");
		return -EPERM;
	}
	assert(ptr != nullptr);
	assert(readSize > 0);
	realDataSize = this->getDataSize();
	size = realDataSize > readSize ? readSize : realDataSize;
	if (!size)
		return size;
	std::lock_guard<std::mutex> lock(this->memoryMutex);

	if (this->readPtr + size > this->tailPtr) {
		memcpy(ptr, this->readPtr, this->tailPtr - this->readPtr);
		copySize = this->tailPtr - this->readPtr;
		memcpy(ptr + copySize, this->headPtr, size - copySize);
		this->readPtr = this->headPtr + size - copySize;
	} else {
		memcpy(ptr, this->readPtr, size);
		this->readPtr = this->readPtr + size;
	}
	this->remainingSize = this->calcRemainingSize();
	return size;
}

/**
 * @brief recalculate the remaining data size in the ringbuff
 *
 * @return int32_t remainning data size in ringbuff
 */
int32_t RingBuffer::calcRemainingSize()
{
	int32_t dataSize = 0;
	if (this->writePtr >= this->readPtr)
		dataSize = this->writePtr - this->readPtr;
	else
		dataSize = this->capacity - (this->readPtr - this->writePtr);

	return (this->capacity - dataSize);
}

/**
 * @brief get the remaing data size in the ringbuff
 *
 * @return int32_t actual remaining size
 */
int32_t RingBuffer::getRemainingSize()
{
	return this->remainingSize;
}

/**
 * @brief get the actual data size in the buff
 *
 * @return int32_t actual data size
 */
int32_t RingBuffer::getDataSize()
{
	return (this->capacity - this->remainingSize);
}

#endif